﻿using iTool.Cloud.Center.Logger;
using iTool.Cloud.Center.Model;
using iTool.ClusterComponent.Implementation.Details;
using iTool.ClusterComponent.Metrics;
using iTool.ClusterComponent.Metrics.Details;
using iTool.Clustering.Center;
using iTool.Common;
using iTool.Common.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;
using Orleans.Runtime;
using Orleans.Runtime.Placement;
using Orleans.Statistics;
using Orleans.Storage;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace iTool.ClusterComponent
{
    public class iToolClusterHostServer : IClusterService
    {
        ISiloHost iHost;
        IGrainFactory iGrainFactory;
        IServiceProvider ServiceProvider;

        public iToolClusterHostServer(ISiloHost host)
        {
            this.iHost = host;
            this.ServiceProvider = this.iHost.Services;
            this.iGrainFactory = this.iHost.Services.GetService<IGrainFactory>();
            iBox.RegisterServiceProvider(this.iHost.Services, "SiloHostService");
        }

        public async Task StopAsync()
        {
            Console.WriteLine("\niTool> Stoping...");
            await this.iHost.StopAsync();
            await this.iHost.DisposeAsync();
            Console.WriteLine("\niTool> Stoped successfully");
        }


        public T GetService<T>(Guid primaryKey) where T : iToolServiceWithGuidKey
        {
            return this.iGrainFactory.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(long primaryKey) where T : iToolServiceWithIntegerKey
        {
            return this.iGrainFactory.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(string primaryKey) where T : iToolServiceWithStringKey
        {
            return this.iGrainFactory.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(long primaryKey1, string primaryKey2) where T : iToolServiceWithIntegerCompoundKey
        {
            return this.iGrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }

        public T GetService<T>(Guid primaryKey1, string primaryKey2) where T : iToolServiceWithGuidCompoundKey
        {
            return this.iGrainFactory.GetGrain<T>(primaryKey1, primaryKey2);
        }

        public T GetService<T>() where T : iToolService
        {
            return this.iGrainFactory.GetGrain<T>(0);
        }

        public T GetServiceByClusterNoder<T>(string targetHost) where T : iToolServiceWithNoder
        {
            return this.iGrainFactory.GetGrain<T>(targetHost);
        }

        public IManagement GetManagementer() => new ManagementService(this.iGrainFactory.GetGrain<IManagementGrain>(0));

        public T GetInterface<T>()
        {
            return this.ServiceProvider.GetService<T>();
        }
    }



    public class iToolHostBuilder
    {
        readonly SiloHostBuilder builder;
        private AdoNetClusterOptions clusterOptions;
        private string ClusterId;
        private const string Invariant = "System.Data.SqlClient";

        public iToolHostBuilder()
        {
            iPrint.Line("\niTool> Start Builder iToolHost...");

            this.builder = new SiloHostBuilder();
            this.Default();
            this.UseMoniterService();
        }

        void Default()
        {
            this.builder.AddCustomStorageBasedLogConsistencyProvider();
            this.builder.AddCustomStorageBasedLogConsistencyProvider("CustomStorage");
            this.builder.AddSimpleMessageStreamProvider("iToolSimpleStream");
            this.builder.Configure<ClusterMembershipOptions>(options =>
            {
                options.DeathVoteExpirationTimeout = TimeSpan.FromSeconds(30);
                options.IAmAliveTablePublishTimeout = TimeSpan.FromSeconds(19);
                options.TableRefreshTimeout = TimeSpan.FromSeconds(30);
            });

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                this.builder.UseLinuxEnvironmentStatistics();
            }
            else
            {
                this.builder.UsePerfCounterEnvironmentStatistics();
            }
        }

        public iToolHostBuilder UseiCenterClustering(ClusterIdentificationOptions identificationOptions, EndpointsOptions endpointsOptions, ClusteringStaticOptions configureOptions) 
        {
            // 自己的集群配置
            this.builder.Configure<ClusterOptions>(options => {
                options.ClusterId = identificationOptions.ClusterId;
                options.ServiceId = identificationOptions.ServiceId;
            });

            this.ClusterId = identificationOptions.ClusterId;

            // 设置服务放置策略
            this.ConfigureServices(services =>
            {
                services.AddSingletonNamedService<PlacementStrategy, ClusterPlacementStrategy>(nameof(ClusterPlacementStrategy));
                services.AddSingletonKeyedService<Type, IPlacementDirector, ClusterSynchronizationProvider>(typeof(ClusterPlacementStrategy));
            });

            // 自己的外网配置
            if (endpointsOptions.AdvertisedIP != null)
            {
                this.builder.ConfigureEndpoints(
                    endpointsOptions.AdvertisedIP,
                    endpointsOptions.Port,
                    endpointsOptions.GatewayPort, true);
            }
            else
            {
                this.builder.ConfigureEndpoints(endpointsOptions.Port, endpointsOptions.GatewayPort, listenOnAnyHostAddress: true);
            }

            // iCenter 的配置
            this.builder.SetiCenterClustering(configureOptions);
            this.builder.UseiCenterClustering();
            this.builder.UseiCenterReminders();
            this.builder.UseiCenterStorage();
            this.builder.UseiCenterStorage("PubSubStore");
            this.builder.UseiCenterTransactional();
            this.ConfigureLogging(build => build.AddCenterLogger(config => 
            {
                config.ProviderName = string.Format("Service_{0}", identificationOptions.ClusterId);
            }));
            return this;
        }

        public iToolHostBuilder UseAdoNetClustering(AdoNetClusterOptions clusterOptions)
        {
            this.builder.Configure<ClusterOptions>(options => {
                options.ClusterId = clusterOptions.ClusterOptions.ClusterId;
                options.ServiceId = clusterOptions.ClusterOptions.ServiceId;
            });

            this.ClusterId = clusterOptions.ClusterOptions.ClusterId;

            this.clusterOptions = clusterOptions;

            this.ConfigureServices(services =>
            {
                services.AddSingleton(clusterOptions.AdoNetOptions);
                services.AddSingleton<IKeyValueStorageProvider, SQLServerKeyValueStorageProvider>();
                services.AddSingleton<IQueueStorageProvider, SQLServerQueueStorageProvider>();

                services.AddSingletonNamedService("KeyValueStorageService", (s, n) => (IGrainStorage)new KeyValueStorageService(s.GetService<IKeyValueStorageProvider>()));
                services.AddSingletonNamedService("HashStorageService", (s, n) => (IGrainStorage)new HashStorageService(clusterOptions.AdoNetOptions));
                services.AddSingletonNamedService("SetStorageService", (s, n) => (IGrainStorage)new SetStorageService(clusterOptions.AdoNetOptions));
                services.AddSingletonNamedService("ZSetStorageService", (s, n) => (IGrainStorage)new ZSetStorageService(clusterOptions.AdoNetOptions));

                services.AddSingletonNamedService<PlacementStrategy, ClusterPlacementStrategy>(nameof(ClusterPlacementStrategy));
                services.AddSingletonKeyedService<Type, IPlacementDirector, ClusterSynchronizationProvider>(typeof(ClusterPlacementStrategy));

            });

            this.builder.Configure<SiloMessagingOptions>(options =>
            {
                options.ResponseTimeout = clusterOptions.ResponseTimeout;
                options.ResponseTimeoutWithDebugger = clusterOptions.ResponseTimeout;
            });

            if (clusterOptions.EndpointsOptions.AdvertisedIP != null)
            {
                this.builder.ConfigureEndpoints(
                    clusterOptions.EndpointsOptions.AdvertisedIP,
                    clusterOptions.EndpointsOptions.Port,
                    clusterOptions.EndpointsOptions.GatewayPort, true);
            }
            else
            {
                this.builder.ConfigureEndpoints(clusterOptions.EndpointsOptions.Port, clusterOptions.EndpointsOptions.GatewayPort, listenOnAnyHostAddress: true);
            }


            builder.UseiToolStorageAsDefault();

            builder.AddAdoNetGrainStorage("PubSubStore", options =>
            {
                options.Invariant = Invariant;
                options.UseJsonFormat = false;
                options.UseXmlFormat = false;
                options.ConnectionString = clusterOptions.AdoNetOptions.GetConnection();
            });

            this.builder.UseAdoNetClustering(options =>
            {
                options.Invariant = Invariant;
                options.ConnectionString = clusterOptions.AdoNetOptions.GetConnection();
            });

            this.builder.UseAdoNetReminderService(options =>
            {
                options.Invariant = Invariant;
                options.ConnectionString = clusterOptions.AdoNetOptions.GetConnection();
            });

            return this;
        }

        /// <summary>
        /// 启用流（队列）服务。
        /// 消息缓存用于，用户消费失败后进行重试；或者消费者从指定Offset开始消费。
        /// 如使用 UseiCenterClustering 集群请手动注册 AdoNetOptions Service， 否则报错
        /// </summary>
        /// <param name="streamName">流名称</param>
        /// <param name="numOfQueue">流队列数量</param>
        /// <param name="dataMaxAgeInCache">消息在内存中的最大缓存时间</param>
        /// <returns></returns>
        public iToolHostBuilder UseStreamProvider(string streamName, int numOfQueue = 7, TimeSpan dataMaxAgeInCache = default)
        {

            dataMaxAgeInCache = dataMaxAgeInCache == default ? TimeSpan.FromMinutes(10) : dataMaxAgeInCache;

            this.builder.UseStreams(streamName, options =>
            {
                options.ConfigurePartitioning(numOfQueue);
                options.ConfigureCacheEviction(op => op.Configure(s => s.DataMaxAgeInCache = dataMaxAgeInCache));
            });

            return this;
        }

        /// <summary>
        /// 配置日志
        /// </summary>
        /// <returns></returns>
        public iToolHostBuilder ConfigureLogging(Action<ILoggingBuilder> configureLogging)
        {
            this.builder.ConfigureLogging(configureLogging);
            return this;
        }

        /// <summary>
        /// 配置Service
        /// </summary>
        /// <returns></returns>
        public iToolHostBuilder ConfigureServices(Action<IServiceCollection> configureDelegate)
        {
            this.builder.ConfigureServices(configureDelegate);
            return this;
        }

        public iToolHostBuilder AddSingleton<TService, TImplementation>()
            where TService : class
            where TImplementation : class, TService
        {
            this.ConfigureServices(services => services.AddSingleton<TService, TImplementation>());
            return this;
        }

        /// <summary>
        /// 服务启动时执行
        /// </summary>
        /// <typeparam name="TStartup"></typeparam>
        /// <returns></returns>
        public iToolHostBuilder AddStartupTask<TStartup>() where TStartup : class, IStartupTask
        {
            this.builder.AddStartupTask<TStartup>();
            return this;
        }

        /// <summary>
        /// 生成并且启动服务
        /// </summary>
        /// <returns></returns>
        public async Task<iToolClusterHostServer> BuildAndStartAsync()
        {
            if (this.clusterOptions != null)
            {
                await this.clusterOptions.CheckDataStructuresAsync();
            }

            this.AddFromAppDomain();

            var host = this.builder.Build();

            await host.StartAsync();

            var server = new iToolClusterHostServer(host);

            await Task.Delay(1000);
            //await this.BuilderAndStartClientAsync();

            new ClusterHostClient(host.Services.GetService<IClusterClient>(), this.ClusterId);

            var dashboardGrain = server.GetService<IDashboardService>();
            await dashboardGrain.InitAsync();

            iPrint.Line("\niTool> Started successfully.");

            return server;
        }

        




        //private async Task BuilderAndStartClientAsync() 
        //{
        //    iPrint.Working("iTool> Streame Start...");
        //    var clientBuilder = new iToolClientBuilder();
        //    clientBuilder.UseAdoNetClustering(new AdoNetClusterOptions
        //    {
        //        AdoNetOptions = this.clusterOptions.AdoNetOptions,
        //        ClusterOptions = this.clusterOptions.ClusterOptions,
        //        ResponseTimeout = TimeSpan.FromSeconds(6)
        //    });
        //    var cluster = await clientBuilder.BuildAndConnectAsync();
        //    iPrint.Success("iTool> Streame Start successfully");
        //}

        /// <summary>
        /// 加载当前域所有iTool Service
        /// </summary>
        /// <returns></returns>
        private iToolHostBuilder AddFromAppDomain()
        {
            this.builder.ConfigureApplicationParts(parts => parts.AddFromApplicationBaseDirectory().WithCodeGeneration().WithReferences());
            return this;
        }

        private iToolHostBuilder UseMoniterService()
        {
            this.ConfigureServices(services =>
            {
                services.Configure<TelemetryOptions>(options => options.AddConsumer<DashboardTelemetryConsumer>());
                services.AddSingleton<SiloStatusOracleSiloDetailsProvider>();
                services.AddSingleton<MembershipTableSiloDetailsProvider>();
                services.AddSingleton<IGrainProfiler, GrainProfiler>();
                services.AddSingleton(c => (ILifecycleParticipant<ISiloLifecycle>)c.GetRequiredService<IGrainProfiler>());
                services.AddSingleton<IIncomingGrainCallFilter, GrainProfilerFilter>();
                services.AddSingleton<ISiloDetailsProvider>(c =>
                {
                    var membershipTable = c.GetService<IMembershipTable>();

                    if (membershipTable != null)
                    {
                        return c.GetRequiredService<MembershipTableSiloDetailsProvider>();
                    }

                    return c.GetRequiredService<SiloStatusOracleSiloDetailsProvider>();
                });

                services.TryAddSingleton(GrainProfilerFilter.NoopOldGrainMethodFormatter);
                services.TryAddSingleton(GrainProfilerFilter.DefaultGrainMethodFormatter);
            });

            return this;
        }


        void config() 
        {
            //this.builder.Configure<ClusterMembershipOptions>(options => {
            //    // 成员资格表中死亡投票的过期时间（秒）。
            //    options.DeathVoteExpirationTimeout = TimeSpan.FromSeconds(60 * 2);
            //    // 失效筒仓清理期
            //    options.DefunctSiloCleanupPeriod = TimeSpan.FromHours(7);
            //    // 失效筒仓的成员资格条目有资格删除的时间段。 只有在奥尔良才有效。配置ClusterMembership选项。已失效的IloCleanUpperIOD不为null。
            //    options.DefunctSiloExpiration = TimeSpan.FromDays(7);
            //    // 是否通过其他思洛存储器间接启用探测思洛存储器。
            //    options.EnableIndirectProbes = true;
            //    // 定期在成员资格表中写入此思洛存储器处于活动状态的秒数。仅用于诊断
            //    options.IAmAliveTablePublishTimeout = TimeSpan.FromSeconds(5 * 60);
            //    // 记录局部健康状况恶化状态的自检间隔时间。
            //    options.LocalHealthDegradationMonitoringPeriod = TimeSpan.FromSeconds(10);
            //    // 放弃前尝试加入思洛存储器群集的秒数。
            //    options.MaxJoinAttemptTime = TimeSpan.FromSeconds(5 * 60);
            //    // 来自思洛存储器的丢失的“我还活着”心跳消息的数量或导致怀疑此思洛存储器已死亡的未回复探测的数量。
            //    options.NumMissedProbesLimit = 3;
            //    // 从导致记录警告的思洛存储器在表中错过的“我还活着”更新的次数。不会影响活动性协议。
            //    options.NumMissedTableIAmAliveLimit = 2;
            //    // 每个思洛存储器探测活动性的思洛存储器数量。
            //    options.NumProbedSilos = 3;
            //    // 宣布某个思洛存储器已死亡所需的未过期票数（最多应为NumMissedProbesLimit）
            //    options.NumVotesForDeathDeclaration = 2;
            //    // 周期性地探测其他思洛存储器的活动性或该思洛存储器发送关于自身的“我还活着”心跳消息的秒数。
            //    options.ProbeTimeout = TimeSpan.FromSeconds(5);
            //    // 定期从成员资格表获取更新的秒数。
            //    options.TableRefreshTimeout = TimeSpan.FromSeconds(60);
            //    // 是否使用八卦优化来加速传播活跃度信息。
            //    options.UseLivenessGossip = true;
            //    // 加入集群的新思洛存储器是否必须验证与所有其他活动思洛存储器的初始连接。
            //    options.ValidateInitialConnectivity = true;
            //});
        }
    }
}
